using Microsoft.Xna.Framework;
using SCSEngine.Utils.Entities;
using SCSEngine.Utils.Entities.Process;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SCSEngine.ScreenManagement.Implement.EntityImplement
{
    public abstract class EntityGameScreen : BaseGameScreen, IGameSystem, IEntityManager
    {
        private List<IGameEntity> entities = new List<IGameEntity>();
        private LinkedList<IGameProcessManager> updateProcesses = new LinkedList<IGameProcessManager>();
        private LinkedList<IGameProcessManager> drawProcesses = new LinkedList<IGameProcessManager>();
        private List<IGameEntity> removedEntity = new List<IGameEntity>();

        public EntityGameScreen(IGameScreenManager manager, string name)
            : base(manager, name)
        {
        }

        public override void Initialize()
        {
            this.AddProcessManagers(this);
            this.InitEntities();

            base.Initialize();
        }

        protected abstract void InitEntities();

        protected abstract void AddProcessManagers(IGameSystem gameSystem);

        public void AddEntity(IGameEntity entity)
        {
            foreach (IGameProcessManager procMan in this.updateProcesses)
            {
                object processObj = entity.GetProcess(procMan.ProcessType);
                if (processObj != null && procMan.ProcessType.IsInstanceOfType(processObj))
                {
                    procMan.AddProcess(processObj);
                }
            }
            foreach (IGameProcessManager procMan in this.drawProcesses)
            {
                object processObj = entity.GetProcess(procMan.ProcessType);
                if (processObj != null && procMan.ProcessType.IsInstanceOfType(processObj))
                {
                    procMan.AddProcess(processObj);
                }
            }

            this.entities.Add(entity);
        }

        public void RemoveEntity(IGameEntity entity)
        {
            this.removedEntity.Add(entity);
        }

        private void UnsafeRemoveEntity(IGameEntity entity)
        {
            foreach (IGameProcessManager procMan in this.updateProcesses)
            {
                object processObj = entity.GetProcess(procMan.ProcessType);
                if (processObj != null)
                {
                    procMan.RemoveProcess(processObj);
                }
            }
            foreach (IGameProcessManager procMan in this.drawProcesses)
            {
                object processObj = entity.GetProcess(procMan.ProcessType);
                if (processObj != null)
                {
                    procMan.RemoveProcess(processObj);
                }
            }

            this.entities.Remove(entity);
        }
        private void RealizeRemoveEntities()
        {
            foreach (IGameEntity entity in this.removedEntity)
            {
                this.UnsafeRemoveEntity(entity);
            }

            this.removedEntity.Clear();
        }

        public IEnumerable<IGameEntity> Entities { get { return this.entities; } }

        public override void Update(GameTime gameTime)
        {
            foreach (IGameProcessManager procMan in this.updateProcesses)
            {
                procMan.Run(this.Game, gameTime);
            }

            this.RealizeRemoveEntities();
        }

        public override void Draw(GameTime gameTime)
        {
            foreach (IGameProcessManager procMan in this.drawProcesses)
            {
                procMan.Run(this.Game, gameTime);
            }
        }

        public void AddProcessManager(IGameProcessManager procMan)
        {
            if (procMan != null)
            {
                if (procMan.IsDrawProcess)
                {
                    this.AddPMToContainer(this.drawProcesses, procMan);
                }
                else
                {
                    this.AddPMToContainer(this.updateProcesses, procMan);
                }
            }
        }

        private void AddPMToContainer(LinkedList<IGameProcessManager> processes, IGameProcessManager procMan)
        {
            LinkedListNode<IGameProcessManager> node = processes.First;
            while (node != null)
            {
                if (node.Value.Priority < procMan.Priority)
                {
                    processes.AddBefore(node, procMan);
                    return;
                }

                node = node.Next;
            }

            processes.AddLast(procMan);
        }

        public void RemoveProcessManager(IGameProcessManager procMan)
        {
            this.updateProcesses.Remove(procMan);
            this.drawProcesses.Remove(procMan);
        }

        protected override void OnStateChanged()
        {
            switch (this.State)
            {
                case GameScreenState.Activated:
                    this.StartPMs();
                    break;
                case GameScreenState.Deactivated:
                    this.StopPMs();
                    break;
                case GameScreenState.Paused:
                    this.PausePMs();
                    break;
            }

            base.OnStateChanged();
        }

        private void PausePMs()
        {
            foreach (IGameProcessManager pm in this.updateProcesses)
            {
                pm.Pause(this.Game);
            }
            foreach (IGameProcessManager pm in this.drawProcesses)
            {
                pm.Pause(this.Game);
            }
        }
        private void StopPMs()
        {
            foreach (IGameProcessManager pm in this.updateProcesses)
            {
                pm.Stop(this.Game);
            }
            foreach (IGameProcessManager pm in this.drawProcesses)
            {
                pm.Stop(this.Game);
            }
        }
        private void StartPMs()
        {
            foreach (IGameProcessManager pm in this.updateProcesses)
            {
                pm.Start(this.Game);
            }
            foreach (IGameProcessManager pm in this.drawProcesses)
            {
                pm.Start(this.Game);
            }
        }
    }
}
